﻿using NMF.Collections.ObjectModel;
using NMF.Models;
using NMF.Models.Repository;
using NMF.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TTC2015.JavaRefactoring.Java.Classifiers;
using TTC2015.JavaRefactoring.Java.Containers;
using TTC2015.JavaRefactoring.Java.Members;
using TTC2015.JavaRefactoring.TypeGraphBasic;

namespace TTC2015.JavaRefactoring
{
    public class Refactoring
    {
        private TextWriter log;
        private string permanentStorage;
        private string tmpStorage;

        private ITypeGraph typeGraph;

        public bool CreateProgramGraph(string path)
        {
            Console.WriteLine("Create Program Graph at " + path);
            try
            {
                var xmiPath = Path.ChangeExtension(path, ".xmi");
                var args = string.Format("-jar jamoppc.jar --disable-layout {0} {1}", path, xmiPath);
                log.WriteLine("[Info] Starting JaMoPPC: " + args);
                var jamoppc = Process.Start("java", args);
                jamoppc.WaitForExit();
                log.WriteLine("[Info] JaMoPPC completed");
                var ownContentList = new ObservableList<IClass>();
                var repository = new ModelRepository();
                var model = repository.Resolve(xmiPath);
                ownContentList.AddRange(from cu in model.RootElements.OfType<ICompilationUnit>()
                                        from cl in cu.Classifiers.OfType<IClass>()
                                        where !cl.Name.StartsWith("java") && !cl.Name.StartsWith("sun")
                                        select cl);

                var jamopp2typeGraph = new JavaPGSynchronization();
                typeGraph = null;
                var context = jamopp2typeGraph.Synchronize<ObservableList<IClass>, ITypeGraph>(jamopp2typeGraph.SynchronizationRule<JavaPGSynchronization.OwnClasses2TypeGraph>(),
                    ref ownContentList, ref typeGraph, NMF.Synchronizations.SynchronizationDirection.LeftToRight, NMF.Transformations.ChangePropagationMode.TwoWay);
            }
            catch (Exception ex)
            {
                log.WriteLine("[Error] " + ex.Message);
            }
            return typeGraph != null;
        }

        public void SetLogPath(string path)
        {
            Console.WriteLine("Set logging at " + path);
            log = File.CreateText(path);
        }

        public void SetPermanentStoragePath(string arg0)
        {
            Console.WriteLine("Permanent storage is " + arg0);
            permanentStorage = arg0;
        }

        public bool SynchronizeChanges()
        {
            return true;
        }

        public bool ApplyCreateSuperclass(string[] classNames, string targetName, string targetPackage)
        {
            return false;
        }

        public bool ApplyPullUpMethod(string targetClassName, string methodName, string[] paramTypes)
        {
            return false;
        }

        public void SetTmpPath(string path)
        {
            Console.WriteLine("Temp path is " + path);
            tmpStorage = path;
        }

        public static int Refactor(string path)
        {
            var repository = new ModelRepository();

            var ownContentList = new ObservableList<IClass>();
            var directory = new DirectoryInfo(path);
            if (!directory.Exists)
            {
                Console.WriteLine("The model folder specified does not exist!");
                return 1;
            }

            var stopwatch = new Stopwatch();

            stopwatch.Start();
            var files = new Dictionary<CompilationUnit, FileInfo>();
            foreach (var file in directory.GetFiles("*.xmi"))
            {
                var compilationUnit = repository.Resolve(file.FullName).Children.OfType<CompilationUnit>().FirstOrDefault();
                files.Add(compilationUnit, file);
                ownContentList.AddRange(compilationUnit.Classifiers.OfType<IClass>());
            }
            stopwatch.Stop();
            Console.WriteLine("Loading models complete.");
            Console.WriteLine("Operation took {0:#,##0}ms", stopwatch.ElapsedMilliseconds);

            stopwatch.Restart();
            var jamopp2typeGraph = new JavaPGSynchronization();
            ITypeGraph typeGraph = null;
            var context = jamopp2typeGraph.Synchronize<ObservableList<IClass>, ITypeGraph>(jamopp2typeGraph.SynchronizationRule<JavaPGSynchronization.OwnClasses2TypeGraph>(),
                ref ownContentList, ref typeGraph, NMF.Synchronizations.SynchronizationDirection.LeftToRight, NMF.Transformations.ChangePropagationMode.TwoWay);
            stopwatch.Stop();
            Console.WriteLine("Synchronization complete.");
            Console.WriteLine("Operation took {0:#,##0}ms", stopwatch.ElapsedMilliseconds);

            stopwatch.Restart();
            CreateSuperClass(typeGraph);
            bool cont = true;
            while (cont)
            {
                cont = PullUpMethod(typeGraph);
            }
            stopwatch.Stop();

            Console.WriteLine("Refactoring Done");
            Console.WriteLine("Operation took {0:#,##0}ms", stopwatch.ElapsedMilliseconds);
            stopwatch.Restart();
            foreach (var cl in ownContentList)
            {
                var unit = cl.Parent as CompilationUnit;
                if (unit == null)
                {
                    unit = new CompilationUnit();
                    unit.Classifiers.Add(cl);
                }
                var model = unit.Model;
                FileInfo file;
                if (!files.TryGetValue(unit, out file) || model == null)
                {
                    model = new Model();
                    model.RootElements.Add(unit);
                    file = new FileInfo(cl.Name + ".java");
                    model.ModelUri = new Uri(file.FullName);
                }
                if (file.Exists) file.Delete();
                using (var fs = file.OpenWrite())
                {
                    repository.Serializer.Serialize(model, fs);
                }
            }
            stopwatch.Stop();
            Console.WriteLine("Serialization done.");
            Console.WriteLine("Operation took {0:#,##0}ms", stopwatch.ElapsedMilliseconds);

            return 0;
        }

        public static bool PullUpMethod(ITypeGraph typeGraph)
        {
            var anyRefactoringDone = false;
            foreach (var method in typeGraph.Methods)
            {
                foreach (var signature in method.Signatures)
                {
                    var methodsGroupsToPull = from def in signature.Definitions
                                              where def.Overriding == null
                                              group def by (def.Parent as TClass).ParentClass into methods
                                              select methods;
                    foreach (var methodGroup in methodsGroupsToPull.Where(group => group.Count() >= 2))
                    {
                        if (methodGroup.Key != null)
                        {
                            var first = methodGroup.First();
                            var firstParent = first.Parent as ITClass;
                            methodGroup.Key.Defines.Add(first);
                            firstParent.Defines.Remove(first);
                            foreach (var m in methodGroup.Skip(1))
                            {
                                (m.Parent as ITClass).Defines.Remove(m);
                            }
                            anyRefactoringDone = true;
                        }
                    }
                }
            }
            return anyRefactoringDone;
        }

        public static void CreateSuperClass(ITypeGraph typeGraph)
        {
            var currentBaseClasses = typeGraph.Classes.Where(c => c.ParentClass == null).ToList();
            if (currentBaseClasses.Any())
            {
                var newClass = new TClass();
            }
        }
    }
}
